home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Almathera Ten Pack 3: CDPD 3
/
Almathera Ten on Ten - Disc 3: CDPD3.iso
/
fish
/
001-100
/
001-025
/
002
/
cc
/
cc.c
< prev
next >
Wrap
C/C++ Source or Header
|
1995-03-17
|
22KB
|
781 lines
/************************************************************************
* *
* Copyright (c) 1985, Fred Fish *
* All Rights Reserved *
* *
* This software and/or documentation is released into the *
* public domain for personal, non-commercial use only. *
* Limited rights to use, modify, and redistribute are hereby *
* granted for non-commercial purposes, provided that all *
* copyright notices remain intact and all changes are clearly *
* documented. The author makes no warranty of any kind with *
* respect to this product and explicitly disclaims any implied *
* warranties of merchantability or fitness for any particular *
* purpose. *
* *
************************************************************************
*/
/*
* cc -- C compiler front end for Amiga and Lattice C.
*
* Somewhat AMIGA/Lattice dependent, but can probably be adapted to
* other systems with a minimum of work. I have attempted to keep
* portability in mind as much as possible.
*
*/
char _sccsid[] = "@(#)cc.c 1.8";
#include <stdio.h>
/*
* The following allow use on systems that don't have my macro based
* debugging package. The default, for now, is to assume it is not
* available.
*/
#ifdef DBUG
# include <local/dbug.h>
#else /* !DBUG */
# define DBUG_ENTER(a)
# define DBUG_RETURN(a) return(a)
# define DBUG_VOID_RETURN return
# define DBUG_2(a,b)
# define DBUG_3(a,b,c)
# define DBUG_4(a,b,c,d)
# define DBUG_5(a,b,c,d,e)
# define DBUG_PUSH(a)
#endif /* DBUG */
/*
* IMPLEMENTATION NOTES
*
* Some of the builtin (artificial) limits could be removed by
* using dynamically allocated data structures, such as keeping the
* operand list as a linked list with space for each link supplied
* by malloc. This would certainly increase the code space, while
* reducing the statically allocated data space. The net result
* would probably be a program requiring about the same amount of
* total memory for most day to day usages. When source is not
* available to the end user, maximum flexibility is a must and
* dynamic allocation is the only way to go. In this case however
* it is not clear that the added complexity is worth it, since
* source should be available for anyone wishing to expand the
* limits.
*
* One last note, if you are going to have builtin limits then
* check the @#$%&* things for overflow. Running off the end of
* an array with no indication that something is wrong, other
* than a crash, if definitely unfriendly!
*
*/
/*
* Manifest constants which can be tuned to fit requirements.
*/
#define CMDBUFFERSIZE (1024) /* Size of command buffer for CLI command */
#define MAXOPERANDS (64) /* Maximum number of operands on cmd line */
#define MAXDEFINES (32) /* Maximum number of -D<name> args */
#define MAXUNDEFINES (32) /* Maximum number of -U<name> args */
#define MAXINCDIRS (16) /* Maximum number of -I<filename> args */
#define MAXLIBS (16) /* Maximum number of -l<lib> args */
#define ARGSIZE (64) /* Size of temp args for cmd line */
/*
* Define QUADDEV to be the default place where you want the compiler
* intermediate files (quad files) to be created. For systems with
* 512K or more, the preferred place is the ram disk. However,
* really big compiles may need to be done on a hard disk.
* In either case, the default can be overridden with the -q option.
*/
#define QUADDEV "ram:" /* Keep intermediate files in ram */
/* #define QUADDEV "" */ /* Keep intermediate files in current dir */
/*
* Manifest constants which are generally the same on all systems.
*/
#define EOS '\000' /* End of string character */
/*
* Command line arguments that represent files to be compiled, assembled,
* or linked, are kept track of via an "Operand" array. If, for example,
* the file name is "df0:mydir/junk.c", then the Rootname is
* "df0:mydir/junk", the Basename is "junk", and the Suffix is "c".
* String suffixes are used, rather than single character suffixes, to
* allow use of names with multicharacter suffixes.
*/
struct Operand { /* Info about each operand (non option) */
char *Rootname; /* Name minus any suffix */
char *Basename; /* Name minus any prefix or suffix */
char *Suffix; /* Suffix of operand */
};
static struct Operand Operands[MAXOPERANDS]; /* Table of operands */
static int NOperands = 0; /* Number of operands found */
static char *Defines[MAXDEFINES]; /* Table of defines */
static int NDefines = 0; /* Number of defines */
static char *UnDefines[MAXUNDEFINES]; /* Table of undefines */
static int NUnDefines = 0; /* Number of undefines */
static char *UserInc[MAXINCDIRS]; /* Table of include dirs */
static int NUserInc = 0; /* Number of include dirs */
static char *Libs[MAXLIBS]; /* Table of library args */
static int NLibs = 0; /* Number of library args */
/*
* The command line buffer for child commands is allocated statically,
* rather than as an automatic, to forstall problems with stack limits
* in initial implementations. Hopefully, automatic stack growth will
* be implemented in future release of the C compiler. If nothing
* else, someday I will read the manuals and figure out how to explicitly
* grow the stack...
*
*/
static char Command[CMDBUFFERSIZE]; /* Command line buffer */
static char *EndCommand = Command; /* End of current command */
/*
* Macros to determine the suffix type of a file given a pointer to
* its operand structure.
*/
#define CFILE(op) (strcmp(op->Suffix,"c")==0)
#define SFILE(op) (strcmp(op->Suffix,"s")==0)
#define OFILE(op) (strcmp(op->Suffix,"o")==0)
extern int strcmp ();
/*
* Now some macros to map from unix names to the AMIGA equivalents,
* and to enable abort on control-C.
*/
#ifdef AMIGA
# define system(a) (Execute(a,0,0))
# define ENABLE_ABORT (Enable_Abort = 1)
# define DISABLE_ABORT (Enable_Abort = 0)
# define CHECK_ABORT Check_Abort()
extern int Enable_Abort; /* Enable abort on CNTL-C */
static void Check_Abort (); /* Test for abort requested */
#else
# define ENABLE_ABORT /* Null expansion */
# define DISABLE_ABORT /* Null expansion */
# define CHECK_ABORT /* Null expansion */
#endif /* AMIGA */
/*
* Set list of places to search for various executables, libraries, etc.
* Searched in order, first match wins, null string is current directory.
* Note that these names are used as prefixes exactly as given, so
* device names must end in ':' and directory names must end in '/'.
*
*/
static char *Devices[] = {
"",
"ram:",
"df0:",
"df1:",
NULL
};
static char *BinDirs[] = {
"",
"ram:c/",
"df0:c/",
"df1:c/",
NULL
};
static char *LibDirs[] = {
"",
"ram:lib/",
"df0:lib/",
"df1:lib/",
NULL
};
static char *IncDirs[] = {
"ram:include/",
"df0:include/",
"df1:include/",
NULL
};
/*
* Flags set by command line arguments/
*/
static int cflag; /* -c flag given */
static int Pflag; /* -P flag given */
static int Sflag; /* -S flag given */
static int Vflag; /* -V flag given (non-standard) */
static int ErrCount = 0; /* Count of compile/assemble errors */
static char *outfile = "a.out"; /* Output file name from linker */
static char *QuadDev = QUADDEV; /* Where to keep quad files */
static char *Locate (); /* Find a file */
static void AddToCommand (); /* Add argument to command buffer */
static void InitCommand (); /* Initialize command buffer */
static void Fatal (); /* Quit with fatal error */
static void Warning (); /* Issue warning message */
static void AddOperandToList (); /* Add .c, .s, or .o file to list */
static void CleanObjects (); /* Remove .o for link and go mode */
static void MakeObjects (); /* Reduce to list of object files */
static void ParseCommandLine (); /* Deal with command line */
static void Compile (); /* Translate from .c to .o */
static void Assemble (); /* Translate from .s to .o */
static void Link (); /* Gather .o's into executable */
extern void exit (); /* See exit(2) */
/*
* Main entry point. Note that despite common usage where main is
* essentially of type void, we declare it explicitly to return
* an int, and actually return a value. In most implementations,
* the value returned from main is the exit status of the program.
* Whether this applies to Lattice C or not, I'm not sure yet.
*/
int main (argc, argv)
int argc;
char *argv[];
{
DBUG_ENTER ("main");
ENABLE_ABORT;
ParseCommandLine (argc, argv);
MakeObjects ();
if (!cflag && !Pflag && !Sflag && ErrCount == 0) {
Link ();
CleanObjects ();
}
DBUG_RETURN (0);
}
/*
* The following macro is used to allow optional whitespace between
* an option and it's argument. Argp is left pointing at the option
* and argv and argc are adjusted accordingly if necessary.
*
* Note that there is no check for missing option arguments. In
* particular, -o -V will blindly take -V as the output file name.
*
*/
#define XARG(argc,argv,argp) {if(*++argp==EOS){argp=(*argv++);argc--;}}
static void ParseCommandLine (argc, argv)
int argc;
char **argv;
{
register char *argp;
DBUG_ENTER ("ParseCommandLine");
argc--;
argv++;
while (argc-- > 0) {
CHECK_ABORT;
argp = *argv++;
if (*argp != '-') {
AddOperandToList (argp);
} else {
switch (*++argp) {
case '#':
XARG (argc, argv, argp);
DBUG_PUSH (argp);
break;
case 'c':
cflag++;
break;
case 'D':
XARG (argc, argv, argp);
if (NDefines >= MAXDEFINES) {
Fatal ("too many -D args (%d max)", MAXDEFINES);
}
Defines[NDefines++] = argp;
break;
case 'E':
Warning ("-E unimplemented, converted to -P instead");
Pflag++;
break;
case 'f':
break; /* NOP for now, just eat it */
case 'g':
break; /* NOP for now, just eat it */
case 'I':
XARG (argc, argv, argp);
if (NUserInc >= MAXINCDIRS) {
Fatal ("too many -I args (%d max)", MAXINCDIRS);
}
UserInc[NUserInc++] = argp;
break;
case 'l':
XARG (argc, argv, argp);
if (NLibs > MAXLIBS) {
Fatal ("too many -l args (%d max)", MAXLIBS);
}
Libs[NLibs++] = argp;
break;
case 'O':
break; /* NOP for now, just eat it */
case 'o':
XARG (argc, argv, argp);
outfile = argp;
break;
case 'P':
Pflag++;
break;
case 'q': /* Warning, non-standard */
XARG (argc, argv, argp);
QuadDev = argp;
break;
case 'S':
Sflag++;
Warning ("-S option not yet implemented, ignored");
break;
case 's':
break; /* NOP for now, just eat it */
case 'U':
XARG (argc, argv, argp);
if (NUnDefines >= MAXUNDEFINES) {
Fatal ("too many -U args (%d max)", MAXUNDEFINES);
}
UnDefines[NUnDefines++] = argp;
break;
case 'V':
Vflag++;
break;
default:
Warning ("unknown option '%c'", (char *) *argp);
break;
}
}
}
DBUG_VOID_RETURN;
}
/*
* For each operand, do compilation or assembly as necessary, to
* reduce to an object file in the current directory.
*/
static void MakeObjects ()
{
register int index;
register struct Operand *op;
DBUG_ENTER ("MakeObjects");
for (index = 0; index < NOperands; index++) {
CHECK_ABORT;
op = &Operands[index];
if (NOperands > 1 && (CFILE (op) || SFILE (op))) {
printf ("%s.%s:\n", op -> Rootname, op -> Suffix);
}
if (CFILE (op)) {
Compile (op);
} else if (SFILE (op)) {
Assemble (op);
}
}
DBUG_VOID_RETURN;
}
/*
* Note that commands to cc of the form "-l<name>" get interpreted
* to mean use a library called "name.lib" from the library
* directory.
*/
static void Link ()
{
register int index;
register struct Operand *op;
register char *name;
auto char buffer[ARGSIZE];
DBUG_ENTER ("Link");
InitCommand ();
AddToCommand ("%s ", Locate ("alink", BinDirs));
AddToCommand ("%s", Locate ("Lstartup.obj", LibDirs));
for (index = 0; index < NOperands; index++) {
op = &Operands[index];
if (OFILE (op)) {
name = op -> Rootname;
} else {
name = op -> Basename;
}
AddToCommand ("+%s.o", name);
}
AddToCommand ("%s", " library ");
for (index = 0; index < NLibs; index++) {
sprintf (buffer, "%s.lib", Libs[index]);
AddToCommand ("%s+", Locate (buffer, LibDirs));
}
AddToCommand ("%s+", Locate ("lc.lib", LibDirs));
AddToCommand ("%s", Locate ("amiga.lib", LibDirs));
AddToCommand (" to %s map nil:", outfile);
(void) RunCommand ();
DBUG_VOID_RETURN;
}
/*VARARGS1*/
static void Warning (fmt, arg1, arg2, arg3)
char *fmt;
char *arg1;
char *arg2;
char *arg3;
{
fprintf (stderr, "cc -- warning: ");
fprintf (stderr, fmt, arg1, arg2, arg3);
fprintf (stderr, "\n");
(void) fflush (stderr);
}
/*VARARGS1*/
static void Fatal (fmt, arg1, arg2, arg3)
char *fmt;
char *arg1;
char *arg2;
char *arg3;
{
fprintf (stderr, "cc -- fatal error: ");
fprintf (stderr, fmt, arg1, arg2, arg3);
fprintf (stderr, "\n");
(void) fflush (stderr);
exit (1);
}
/*
* Split an operand name into rootname, basename, and suffix
* components. The rootname is the full name, minus any suffix,
* but including any prefix. The basename is the rootname minus
* any prefix. The suffix is anything after the last '.' character.
* Only the suffix is allowed to be the null string.
*/
static void AddOperandToList (filename)
char *filename;
{
register char *split;
register struct Operand *op;
extern char *strrchr ();
DBUG_ENTER ("AddOperandToList");
DBUG_3 ("ops", "add file '%s' to operand list", filename);
if (NOperands >= MAXOPERANDS) {
Fatal ("too many files (%d max)\n", MAXOPERANDS);
}
op = &Operands[NOperands];
op -> Rootname = filename;
if ((split = strrchr (filename, '/')) == NULL) {
split = strrchr (filename, ':');
}
if (split == NULL) {
op -> Basename = filename;
} else {
op -> Basename = ++split;
}
if ((split = strrchr (filename, '.')) == NULL) {
op -> Suffix = "";
} else {
*split++ = EOS;
op -> Suffix = split;
}
DBUG_3 ("ops", "rootname '%s'", op -> Rootname);
DBUG_3 ("ops", "basename '%s'", op -> Basename);
DBUG_3 ("ops", "suffix '%s'", op -> Suffix);
NOperands++;
DBUG_VOID_RETURN;
}
/*
* Compile one operand from a C source program to an object module.
*/
static void Compile (op)
struct Operand *op;
{
DBUG_ENTER ("Compile");
if (!Sflag && Pass1 (op) && !Pflag) { /* Order important! */
CHECK_ABORT;
(void) Pass2 (op);
}
DBUG_VOID_RETURN;
}
/*
* Note that because of brain-damage in the fact that -p to lc1 removes
* all predefined defs, we must add them so replacing -c with -P in the
* cc command line will result in the same set of predefined symbols.
* This is rather ugly and leaves a hole for future problems if we
* get out of sync with respect to what names the compiler predefines.
*/
static int Pass1 (op)
register struct Operand *op;
{
register int status;
register int index;
DBUG_ENTER ("Pass1");
InitCommand ();
AddToCommand ("%s", Locate ("lc1", BinDirs));
if (Pflag) {
AddToCommand (" -o%s.i -p -DAMIGA -DM68000 -DSPTR", op -> Basename);
} else {
AddToCommand (" -o%s%s.q", QuadDev, op -> Basename);
}
for (index = 0; index <NUserInc; index++) {
AddToCommand (" -i%s/", UserInc[index]);
}
for (index = 0; index <NUnDefines; index++) {
/*************************
AddToCommand (" -u%s", UnDefines[index]);
**************************/
Warning ("-U%s ignored! (unimplemented)", UnDefines[index]);
}
for (index = 0; index <NDefines; index++) {
AddToCommand (" -d%s", Defines[index]);
}
AddToCommand (" -i%s/", Locate ("include", Devices));
AddToCommand (" -i%s/", Locate ("lattice", IncDirs));
AddToCommand (" %s", op -> Rootname);
status = RunCommand ();
DBUG_RETURN (status);
}
/*
* Run second pass of compiler on a single operand.
*/
static int Pass2 (op)
struct Operand *op;
{
int status;
DBUG_ENTER ("Pass2");
InitCommand ();
AddToCommand ("%s", Locate ("lc2", BinDirs));
AddToCommand (" -o%s.o", op -> Basename);
AddToCommand (" %s%s", QuadDev, op -> Basename);
status = RunCommand ();
DBUG_RETURN (status);
}
/*
* I have not yet had occasion to use the macro assembler, so this
* part is not yet implemented. If anyone wants to send me the
* appropriate code, I will be glad to install it.
*/
static void Assemble (op)
struct Operand *op;
{
DBUG_ENTER ("Assemble");
Warning ("assembly pass not yet implemented");
ErrCount++;
DBUG_VOID_RETURN;
}
/*
* As far as I can tell, the child status is not returned, only
* whether or not the child could be run. So, how do we find out
* whether there was an error or not? It's probably in the manuals
* somewhere, I just haven't had time to dig yet.
*
* Note that because Lattice printf is not capable of printing more
* than 200 characters at a time, we must spit them out one at a time
* to make sure the entire command line gets printed when -V is used.
*
*/
static int RunCommand ()
{
int status;
register char *cmdp;
DBUG_ENTER ("RunCommand");
DBUG_3 ("cmd", "execute '%s'", Command);
if (Vflag) {
for (cmdp = Command; *cmdp != '\000'; cmdp++) {
putchar (*cmdp); /* see above */
}
putchar ('\n');
(void) fflush (stdout);
}
CHECK_ABORT;
status = system (Command);
DBUG_3 ("sys", "subcommand returns status %d", status);
if (!status) {
ErrCount++;
}
DBUG_RETURN (status);
}
/*
* Look through the list of paths pointed to by "vec" until we find
* a file with name given pointed to by "namep". If none is found,
* the name pointed to by namep is returned.
*/
static char *Locate (namep, vec)
char *namep;
char **vec;
{
static char namebuf[ARGSIZE];
DBUG_ENTER ("Locate");
while (*vec != NULL) {
(void) sprintf (namebuf, "%s%s", *vec, namep);
DBUG_3 ("try", "look for '%s'", namebuf);
if (Readable (namebuf)) {
namep = namebuf;
break;
}
vec++;
}
DBUG_RETURN (namep);
}
/*
* Check to see if the file exists and is readable.
*/
#ifdef unix
# include <fcntl.h>
#else
# include <libraries/dos.h>
#endif
static int Readable (name)
char *name;
{
register int status = 0;
register int fildes;
DBUG_ENTER ("Readable");
#ifdef unix
fildes = open (name, O_RDONLY);
if (fildes >= 0) {
(void) close (fildes);
status = 1;
}
#else
fildes = Lock (name, ACCESS_READ);
if (fildes != 0) {
UnLock (fildes);
status = 1;
}
#endif
DBUG_RETURN (status);
}
/*
* Do explicit check for abort. When Enable_Abort is non-zero,
* Chk_Abort() cause program termination if CNTRL-C or CNTRL-D has
* been received. Thus, we temporarily set it back to zero while we
* do the explicit test, so we can do our own clean up and exit.
* Note that if the -V flag was used, we spit out a confirming message
* that we are quitting.
*
* Since we previously set Check_Abort to non-zero, this routine may be
* overkill.
*/
#ifdef AMIGA
static void Check_Abort ()
{
extern int Chk_Abort ();
DBUG_ENTER ("Check_Abort");
DBUG_2 ("abort", "do explicit test for CNTRL-C");
DISABLE_ABORT;
if (Chk_Abort () != 0) {
if (Vflag) {
printf ("cc - terminated by request\n");
}
exit (1);
}
ENABLE_ABORT;
DBUG_VOID_RETURN;
}
#endif
/*
* Initialize the command line buffer and associated variables to
* discard any previous command line.
*/
static void InitCommand ()
{
Command[0] = '\000';
EndCommand = Command;
}
/*
* Build string to add to end of current command line, checking
* for overflow in the command buffer and maintaining the pointer
* to the end of the current command.
*
* Note that we are a "printf type" of command, and can be called
* with up to three "char *" arguments. There is a portability
* problem here, but Lattice hasn't yet made "varargs" a standard
* part of their distribution.
*
* Also, note that the return argument of sprintf is supposed to be
* the number of characters to be added to the buffer. This is
* not always true for some C implementations. In particular,
* sprintf in BSD4.1 returns a pointer. Thus we don't use the
* return argument.
*
*/
/*VARARGS1*/
static void AddToCommand (fmt, arg1, arg2, arg3)
char *fmt;
char *arg1, *arg2, *arg3;
{
register int length;
auto char buffer[ARGSIZE];
(void) sprintf (buffer, fmt, arg1, arg2, arg3);
length = strlen (buffer);
if ((EndCommand - Command) + length >= sizeof (Command)) {
Fatal ("command line too long (%d char max)", sizeof (Command));
} else {
(void) strcat (EndCommand, buffer);
EndCommand += length;
}
}
/*
* If an executable is made from a single C file, the normal behavior
* for the unix environment is to treat the .o file as an intermediate
* file and remove it, so we follow suit.
*/
static void CleanObjects ()
{
auto char buffer[ARGSIZE];
register struct Operand *op;
DBUG_ENTER ("CleanObjects");
if (NOperands == 1) {
op = &Operands[0];
if (CFILE (op) || SFILE (op)) {
sprintf (buffer, "%s.o", op -> Basename);
if (!DeleteFile (buffer)) {
Warning ("can't delete '%s'", buffer);
}
}
}
DBUG_VOID_RETURN;
}